Skip to content

OCPBUGS-76350: Honor AWS AMI override in NodePool token generation - #7675

Merged
openshift-merge-bot[bot] merged 2 commits into
openshift:mainfrom
hypershift-community:fix-OCPBUGS-76350
Feb 25, 2026
Merged

OCPBUGS-76350: Honor AWS AMI override in NodePool token generation#7675
openshift-merge-bot[bot] merged 2 commits into
openshift:mainfrom
hypershift-community:fix-OCPBUGS-76350

Conversation

@hypershift-jira-solve-ci

@hypershift-jira-solve-ci hypershift-jira-solve-ci Bot commented Feb 6, 2026

Copy link
Copy Markdown
Contributor

What this PR does / why we need it:

Fixes a bug where the hypershift-operator ignores the AWS AMI override specified in nodePool.Spec.Platform.AWS.AMI when generating the token/user data in NewToken() (token.go). This causes NodePool deployments to fail in AWS ISO (classified) regions where:

  1. Release image metadata (StreamMetadata) may not contain region data for ISO regions
  2. Users must specify a custom AMI via nodePool.Spec.Platform.AWS.AMI to work in these regions

Changes:

  • token.go: Updated NewToken() to check nodePool.Spec.Platform.AWS.AMI before falling back to defaultNodePoolAMI(), aligning with the existing pattern in awsMachineTemplateSpec() (aws.go)
  • nodepool_controller.go: Added nil check for releaseImage.StreamMetadata in defaultNodePoolAMI() to prevent nil pointer dereference when stream metadata is unavailable (consistent with getWindowsAMI())
  • token_test.go: Added test cases for AMI override in token generation, including nil StreamMetadata scenario
  • nodepool_controller_test.go: Added test case for nil StreamMetadata in defaultNodePoolAMI()

Which issue(s) this PR fixes:

Fixes https://issues.redhat.com/browse/OCPBUGS-76350

Special notes for your reviewer:

The fix follows the same AMI resolution pattern already used in awsMachineTemplateSpec() (aws.go lines 41-58), ensuring consistency across both code paths. The bug was causing an inconsistency where the CAPI machine template path honored the AMI override but the token/user-data generation path did not.

Note: There is a pre-existing build failure in secret_janitor_test.go (references to undefined releaseinfo.NewMockProviderWithRegistryOverrides) that is unrelated to these changes.

Checklist:

  • Subject and description added to both, commit and PR.
  • Relevant issues have been referenced.
  • This change includes docs.
  • This change includes unit tests.

🤖 Generated with Claude Code via /jira-solve OCPBUGS-76350 origin

Always review AI generated responses prior to use.

Summary by CodeRabbit

  • Bug Fixes

    • Added validation to return an explicit error when a release image lacks stream metadata, preventing a nil dereference.
  • Tests

    • Added coverage for the release-image-without-stream-metadata scenario.
  • Refactor

    • AMI resolution moved to runtime reconciliation; AMI is no longer embedded in user-data and is set via labels during reconcile.

@openshift-ci-robot

Copy link
Copy Markdown

Pipeline controller notification
This repo is configured to use the pipeline controller. Second-stage tests will be triggered either automatically or after lgtm label is added, depending on the repository configuration. The pipeline controller will automatically detect which contexts are required and will utilize /test Prow commands to trigger the second stage.

For optional jobs, comment /test ? to see a list of all defined jobs. To trigger manually all jobs from second stage use /pipeline required command.

This repository is configured in: LGTM mode

@openshift-ci-robot openshift-ci-robot added jira/severity-important Referenced Jira bug's severity is important for the branch this PR is targeting. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. jira/invalid-bug Indicates that a referenced Jira bug is invalid for the branch this PR is targeting. labels Feb 6, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@hypershift-jira-solve-ci[bot]: This pull request references Jira Issue OCPBUGS-76350, which is invalid:

  • expected the bug to target the "4.22.0" version, but no target version was set

Comment /jira refresh to re-evaluate validity if changes to the Jira bug are made, or edit the title of this pull request to link to a different bug.

The bug has been updated to refer to the pull request using the external bug tracker.

Details

In response to this:

What this PR does / why we need it:

Fixes a bug where the hypershift-operator ignores the AWS AMI override specified in nodePool.Spec.Platform.AWS.AMI when generating the token/user data in NewToken() (token.go). This causes NodePool deployments to fail in AWS ISO (classified) regions where:

  1. Release image metadata (StreamMetadata) may not contain region data for ISO regions
  2. Users must specify a custom AMI via nodePool.Spec.Platform.AWS.AMI to work in these regions

Changes:

  • token.go: Updated NewToken() to check nodePool.Spec.Platform.AWS.AMI before falling back to defaultNodePoolAMI(), aligning with the existing pattern in awsMachineTemplateSpec() (aws.go)
  • nodepool_controller.go: Added nil check for releaseImage.StreamMetadata in defaultNodePoolAMI() to prevent nil pointer dereference when stream metadata is unavailable (consistent with getWindowsAMI())
  • token_test.go: Added test cases for AMI override in token generation, including nil StreamMetadata scenario
  • nodepool_controller_test.go: Added test case for nil StreamMetadata in defaultNodePoolAMI()

Which issue(s) this PR fixes:

Fixes https://issues.redhat.com/browse/OCPBUGS-76350

Special notes for your reviewer:

The fix follows the same AMI resolution pattern already used in awsMachineTemplateSpec() (aws.go lines 41-58), ensuring consistency across both code paths. The bug was causing an inconsistency where the CAPI machine template path honored the AMI override but the token/user-data generation path did not.

Note: There is a pre-existing build failure in secret_janitor_test.go (references to undefined releaseinfo.NewMockProviderWithRegistryOverrides) that is unrelated to these changes.

Checklist:

  • Subject and description added to both, commit and PR.
  • Relevant issues have been referenced.
  • This change includes docs.
  • This change includes unit tests.

🤖 Generated with Claude Code via /jira-solve OCPBUGS-76350 origin

Always review AI generated responses prior to use.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@coderabbitai

coderabbitai Bot commented Feb 6, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Adds a nil-check for releaseImage.StreamMetadata in defaultNodePoolAMI, moves AMI computation out of token initialization into reconcile-time (reconcileUserDataSecret), removes the ami field from userData, and adds a test for the nil StreamMetadata case with lazy release image retrieval.

Changes

Cohort / File(s) Summary
StreamMetadata nil-guard + test
hypershift-operator/controllers/nodepool/nodepool_controller.go, hypershift-operator/controllers/nodepool/nodepool_controller_test.go
Adds an explicit nil-check returning an error when releaseImage.StreamMetadata is nil in defaultNodePoolAMI. Adds a test case covering nil StreamMetadata and alters test setup to fetch the release image lazily when tc.releaseImage is nil.
Token / userData AMI handling
hypershift-operator/controllers/nodepool/token.go
Removes the ami field from userData. Stops computing/setting AMI in NewToken; instead computes AMI during reconcileUserDataSecret when Karpenter is enabled and sets the UserData AMI label with error handling via defaultNodePoolAMI.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Test Structure And Quality ⚠️ Warning Unit tests use bare expectations without descriptive messages, making failures harder to diagnose. Add meaningful assertion messages to all test expectations to clearly state intent and improve debuggability.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly addresses the main change: honoring AWS AMI override in NodePool token generation, which matches the core bug fix involving updated AMI resolution logic in token.go.
Stable And Deterministic Test Names ✅ Passed All test names in modified test files are static and deterministic with no dynamic information such as UUIDs, timestamps, or random suffixes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

@openshift-ci openshift-ci Bot added do-not-merge/needs-area do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. area/hypershift-operator Indicates the PR includes changes for the hypershift operator and API - outside an OCP release labels Feb 6, 2026
@openshift-ci

openshift-ci Bot commented Feb 6, 2026

Copy link
Copy Markdown
Contributor

Hi @hypershift-jira-solve-ci[bot]. Thanks for your PR.

I'm waiting for a openshift member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

Comment on lines +139 to +145
if configGenerator.nodePool.Spec.Platform.AWS != nil && configGenerator.nodePool.Spec.Platform.AWS.AMI != "" {
ami = configGenerator.nodePool.Spec.Platform.AWS.AMI
} else {
ami, err = defaultNodePoolAMI(configGenerator.hostedCluster.Spec.Platform.AWS.Region, configGenerator.nodePool.Spec.Arch, configGenerator.releaseImage)
if err != nil {
return nil, err
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the ami field of token.userData is only used for karpenter token secrets here

userDataSecret.Labels[hyperkarpenterv1.UserDataAMILabel] = t.userData.ami

Karpetner is a separate feature that doesn't use hypershift nodePools. I would rather we only fetch the default ami and set the field when karpenter is enabled instead of this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Good catch — moved AMI resolution out of NewToken() entirely and into the Karpenter-only path in reconcileUserDataSecret(). The AMI is now only resolved via a new resolveAMI() helper when IsKarpenterEnabled is true and the nodePool is the Karpenter nodePool, which avoids unnecessary calls to defaultNodePoolAMI() for non-Karpenter scenarios (and the nil StreamMetadata error in ISO regions).


AI-assisted response via Claude Code

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved AMI resolution out of NewToken() entirely and into the Karpenter-only path in reconcileUserDataSecret()

Does this change make us fetch the default ami multiple each reconcile loop? or was this always the case?

@muraee

muraee commented Feb 9, 2026

Copy link
Copy Markdown
Contributor

/test review-agent-single-pr

@@ -372,8 +362,12 @@ func (t *Token) reconcileUserDataSecret(userDataSecret *corev1.Secret, token str
if karpenterutil.IsKarpenterEnabled(t.hostedCluster.Spec.AutoNode) {
// TODO(alberto): prevent nodePool name collisions adding prefix to karpenter NodePool.
if t.nodePool.GetName() == hyperkarpenterv1.KarpenterNodePool {
ami, err := t.resolveAMI()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the new resolveAMI is not needed, as I explained before. karpetner doesn't use any of the NodePools spec fields, so checking if t.nodePool.Spec.Platform.AWS.AMI is useless.

Just used defaultNodePoolAMI() here directly

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Removed resolveAMI() and calling defaultNodePoolAMI() directly in the Karpenter block. Also removed the TestResolveAMI test since the helper no longer exists.

To answer your earlier question: this doesn't change the frequency of defaultNodePoolAMI() calls — before, it was called in NewToken() which runs every reconcile. Now it's called in reconcileUserDataSecret(), which also runs every reconcile, but only when Karpenter is enabled and it's the Karpenter NodePool. So it's actually called less often now (only for Karpenter scenarios), not more.


AI-assisted response via Claude Code

@muraee muraee Feb 16, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did you revert this change and restored resolveAMI()? Just call defaultNodePoolAMI() directly in the Karpenter block.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Removed resolveAMI() and restored the direct defaultNodePoolAMI() call in NewToken().


AI-assisted response via Claude Code

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

defaultNodePoolAMI() should be called in reconcileUserDataSecret() in the Karpenter block where it's used.
the ami field in the userToken is not needed

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Moved defaultNodePoolAMI() call from NewToken() into reconcileUserDataSecret() in the Karpenter block where it's used, and removed the ami field from the userData struct. The AMI override check (nodePool.Spec.Platform.AWS.AMI) is now also applied directly in that block.


AI-assisted response via Claude Code

@muraee

muraee commented Feb 9, 2026

Copy link
Copy Markdown
Contributor

/test review-agent-single-pr

@muraee

muraee commented Feb 9, 2026

Copy link
Copy Markdown
Contributor

/ok-to-test
/lgtm

@openshift-ci openshift-ci Bot added ok-to-test Indicates a non-member PR verified by an org member that is safe to test. and removed needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. labels Feb 9, 2026
@openshift-ci openshift-ci Bot added the lgtm Indicates that a PR is ready to be merged. label Feb 9, 2026
@openshift-ci-robot

Copy link
Copy Markdown

Scheduling required tests:
/test verify-deps

Scheduling tests matching the pipeline_run_if_changed or not excluded by pipeline_skip_if_only_changed parameters:
/test e2e-aks-4-21
/test e2e-aws-4-21
/test e2e-aks
/test e2e-aws
/test e2e-aws-upgrade-hypershift-operator
/test e2e-kubevirt-aws-ovn-reduced
/test e2e-v2-aws

@bryan-cox

Copy link
Copy Markdown
Member

/retest

@openshift-merge-robot openshift-merge-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Feb 10, 2026
@bryan-cox

Copy link
Copy Markdown
Member

hypershift-jira-solve-ci - this PR needs rebased to fix the label issue

@openshift-ci openshift-ci Bot removed the lgtm Indicates that a PR is ready to be merged. label Feb 16, 2026
@openshift-ci
openshift-ci Bot requested a review from jimma February 19, 2026 14:09
@openshift-ci-robot

Copy link
Copy Markdown

@hypershift-jira-solve-ci[bot]: This pull request references Jira Issue OCPBUGS-76350, which is valid.

3 validation(s) were run on this bug
  • bug is open, matching expected state (open)
  • bug target version (4.22.0) matches configured target version for branch (4.22.0)
  • bug is in the state POST, which is one of the valid states (NEW, ASSIGNED, POST)

Requesting review from QA contact:
/cc @jimma

Details

In response to this:

What this PR does / why we need it:

Fixes a bug where the hypershift-operator ignores the AWS AMI override specified in nodePool.Spec.Platform.AWS.AMI when generating the token/user data in NewToken() (token.go). This causes NodePool deployments to fail in AWS ISO (classified) regions where:

  1. Release image metadata (StreamMetadata) may not contain region data for ISO regions
  2. Users must specify a custom AMI via nodePool.Spec.Platform.AWS.AMI to work in these regions

Changes:

  • token.go: Updated NewToken() to check nodePool.Spec.Platform.AWS.AMI before falling back to defaultNodePoolAMI(), aligning with the existing pattern in awsMachineTemplateSpec() (aws.go)
  • nodepool_controller.go: Added nil check for releaseImage.StreamMetadata in defaultNodePoolAMI() to prevent nil pointer dereference when stream metadata is unavailable (consistent with getWindowsAMI())
  • token_test.go: Added test cases for AMI override in token generation, including nil StreamMetadata scenario
  • nodepool_controller_test.go: Added test case for nil StreamMetadata in defaultNodePoolAMI()

Which issue(s) this PR fixes:

Fixes https://issues.redhat.com/browse/OCPBUGS-76350

Special notes for your reviewer:

The fix follows the same AMI resolution pattern already used in awsMachineTemplateSpec() (aws.go lines 41-58), ensuring consistency across both code paths. The bug was causing an inconsistency where the CAPI machine template path honored the AMI override but the token/user-data generation path did not.

Note: There is a pre-existing build failure in secret_janitor_test.go (references to undefined releaseinfo.NewMockProviderWithRegistryOverrides) that is unrelated to these changes.

Checklist:

  • Subject and description added to both, commit and PR.
  • Relevant issues have been referenced.
  • This change includes docs.
  • This change includes unit tests.

🤖 Generated with Claude Code via /jira-solve OCPBUGS-76350 origin

Always review AI generated responses prior to use.

Summary by CodeRabbit

  • Bug Fixes

  • Added validation to prevent a potential nil reference error when processing release images with missing metadata.

  • Tests

  • Added test coverage for the release image validation scenario.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

The NewToken() function in token.go always called defaultNodePoolAMI()
without checking if a user-specified AMI override was set via
nodePool.Spec.Platform.AWS.AMI. This caused failures in AWS ISO
(classified) regions where release image metadata may not contain
region data, and users must specify a custom AMI.

This fix aligns the AMI resolution logic in NewToken() with the
existing pattern in awsMachineTemplateSpec() (aws.go), which correctly
checks for the AMI override before falling back to defaultNodePoolAMI().

Additionally, a nil check for releaseImage.StreamMetadata is added to
defaultNodePoolAMI() to prevent a nil pointer dereference when stream
metadata is unavailable, consistent with the existing nil check in
getWindowsAMI().

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@@ -377,7 +367,17 @@ func (t *Token) reconcileUserDataSecret(userDataSecret *corev1.Secret, token str
if karpenterutil.IsKarpenterEnabled(t.hostedCluster.Spec.AutoNode) {
npLabels := t.nodePool.GetLabels()
if npLabels != nil && npLabels[karpenterutil.ManagedByKarpenterLabel] == "true" {
userDataSecret.Labels[hyperkarpenterv1.UserDataAMILabel] = t.userData.ami
ami := ""
if t.nodePool.Spec.Platform.AWS != nil && t.nodePool.Spec.Platform.AWS.AMI != "" {

@muraee muraee Feb 19, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't read AMI from nodePool. Kerpenter doesn't use NodePool spec.
calling defaultNodePoolAMI is enough

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Removed the NodePool AMI override check — now only calling defaultNodePoolAMI() directly in the Karpenter block.


AI-assisted response via Claude Code

@muraee

muraee commented Feb 19, 2026

Copy link
Copy Markdown
Contributor

/test address-review-comments

Add test cases to verify that:
- When a NodePool specifies an AMI override via Spec.Platform.AWS.AMI,
  NewToken() uses the override instead of calling defaultNodePoolAMI()
- When an AMI override is set and StreamMetadata is nil (as in AWS ISO
  regions), token creation succeeds without errors
- defaultNodePoolAMI() returns a clear error when StreamMetadata is nil

These tests ensure the AMI override behavior is consistent between
the token generation path and the machine template generation path.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@muraee

muraee commented Feb 19, 2026

Copy link
Copy Markdown
Contributor

/lgtm

@openshift-ci openshift-ci Bot added the lgtm Indicates that a PR is ready to be merged. label Feb 19, 2026
@openshift-ci-robot

Copy link
Copy Markdown

Scheduling tests matching the pipeline_run_if_changed or not excluded by pipeline_skip_if_only_changed parameters:
/test e2e-aks-4-21
/test e2e-aws-4-21
/test e2e-aks
/test e2e-aws
/test e2e-aws-upgrade-hypershift-operator
/test e2e-kubevirt-aws-ovn-reduced
/test e2e-v2-aws

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@hypershift-operator/controllers/nodepool/token.go`:
- Around line 367-375: The Karpenter path always calls defaultNodePoolAMI and
ignores any user-specified AMI override; update the block guarded by
karpenterutil.IsKarpenterEnabled (and the t.nodePool label check) to first
inspect t.nodePool.Spec.Platform?.AWS?.AMI and, if non-empty, use that AMI value
for userDataSecret.Labels[hyperkarpenterv1.UserDataAMILabel], otherwise fall
back to calling defaultNodePoolAMI(t.hostedCluster.Spec.Platform.AWS.Region,
t.nodePool.Spec.Arch, t.releaseImage); ensure you safely handle nil Platform/AWS
pointers and preserve setting karpenterutil.ManagedByKarpenterLabel="true".

Comment on lines 367 to 375
if karpenterutil.IsKarpenterEnabled(t.hostedCluster.Spec.AutoNode) {
npLabels := t.nodePool.GetLabels()
if npLabels != nil && npLabels[karpenterutil.ManagedByKarpenterLabel] == "true" {
userDataSecret.Labels[hyperkarpenterv1.UserDataAMILabel] = t.userData.ami
ami, err := defaultNodePoolAMI(t.hostedCluster.Spec.Platform.AWS.Region, t.nodePool.Spec.Arch, t.releaseImage)
if err != nil {
return fmt.Errorf("failed to get default node pool AMI: %w", err)
}
userDataSecret.Labels[hyperkarpenterv1.UserDataAMILabel] = ami
userDataSecret.Labels[karpenterutil.ManagedByKarpenterLabel] = "true"

@coderabbitai coderabbitai Bot Feb 19, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

rg -n "defaultNodePoolAMI" --type=go -C3

Repository: openshift/hypershift

Length of output: 3778


🏁 Script executed:

sed -n '60,80p' hypershift-operator/controllers/nodepool/aws.go

Repository: openshift/hypershift

Length of output: 866


🏁 Script executed:

sed -n '300,320p' hypershift-operator/controllers/nodepool/aws.go

Repository: openshift/hypershift

Length of output: 1132


🏁 Script executed:

rg "Platform\.AWS\.AMI" --type=go -B2 -A2

Repository: openshift/hypershift

Length of output: 1187


🏁 Script executed:

sed -n '45,90p' hypershift-operator/controllers/nodepool/aws.go

Repository: openshift/hypershift

Length of output: 1793


🏁 Script executed:

sed -n '360,380p' hypershift-operator/controllers/nodepool/token.go

Repository: openshift/hypershift

Length of output: 991


Honor NodePool AMI override before falling back to defaultNodePoolAMI.

Lines 370–374 compute the AMI solely via defaultNodePoolAMI(...), ignoring any user-specified nodePool.Spec.Platform.AWS.AMI. This creates an inconsistency: the awsMachineTemplateSpec function in aws.go checks for the override first, but the Karpenter path in token.go does not. The UserDataAMILabel will therefore be set to the default AMI even when a custom AMI is specified, breaking consistency for Karpenter and failing in ISO/air-gapped environments that require custom AMIs.

✅ Proposed fix
-			ami, err := defaultNodePoolAMI(t.hostedCluster.Spec.Platform.AWS.Region, t.nodePool.Spec.Arch, t.releaseImage)
-			if err != nil {
-				return fmt.Errorf("failed to get default node pool AMI: %w", err)
-			}
+			ami := ""
+			if t.nodePool.Spec.Platform.AWS != nil && t.nodePool.Spec.Platform.AWS.AMI != "" {
+				ami = t.nodePool.Spec.Platform.AWS.AMI
+			} else {
+				var err error
+				ami, err = defaultNodePoolAMI(t.hostedCluster.Spec.Platform.AWS.Region, t.nodePool.Spec.Arch, t.releaseImage)
+				if err != nil {
+					return fmt.Errorf("failed to get default node pool AMI: %w", err)
+				}
+			}
 			userDataSecret.Labels[hyperkarpenterv1.UserDataAMILabel] = ami
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if karpenterutil.IsKarpenterEnabled(t.hostedCluster.Spec.AutoNode) {
npLabels := t.nodePool.GetLabels()
if npLabels != nil && npLabels[karpenterutil.ManagedByKarpenterLabel] == "true" {
userDataSecret.Labels[hyperkarpenterv1.UserDataAMILabel] = t.userData.ami
ami, err := defaultNodePoolAMI(t.hostedCluster.Spec.Platform.AWS.Region, t.nodePool.Spec.Arch, t.releaseImage)
if err != nil {
return fmt.Errorf("failed to get default node pool AMI: %w", err)
}
userDataSecret.Labels[hyperkarpenterv1.UserDataAMILabel] = ami
userDataSecret.Labels[karpenterutil.ManagedByKarpenterLabel] = "true"
if karpenterutil.IsKarpenterEnabled(t.hostedCluster.Spec.AutoNode) {
npLabels := t.nodePool.GetLabels()
if npLabels != nil && npLabels[karpenterutil.ManagedByKarpenterLabel] == "true" {
ami := ""
if t.nodePool.Spec.Platform.AWS != nil && t.nodePool.Spec.Platform.AWS.AMI != "" {
ami = t.nodePool.Spec.Platform.AWS.AMI
} else {
var err error
ami, err = defaultNodePoolAMI(t.hostedCluster.Spec.Platform.AWS.Region, t.nodePool.Spec.Arch, t.releaseImage)
if err != nil {
return fmt.Errorf("failed to get default node pool AMI: %w", err)
}
}
userDataSecret.Labels[hyperkarpenterv1.UserDataAMILabel] = ami
userDataSecret.Labels[karpenterutil.ManagedByKarpenterLabel] = "true"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hypershift-operator/controllers/nodepool/token.go` around lines 367 - 375,
The Karpenter path always calls defaultNodePoolAMI and ignores any
user-specified AMI override; update the block guarded by
karpenterutil.IsKarpenterEnabled (and the t.nodePool label check) to first
inspect t.nodePool.Spec.Platform?.AWS?.AMI and, if non-empty, use that AMI value
for userDataSecret.Labels[hyperkarpenterv1.UserDataAMILabel], otherwise fall
back to calling defaultNodePoolAMI(t.hostedCluster.Spec.Platform.AWS.Region,
t.nodePool.Spec.Arch, t.releaseImage); ensure you safely handle nil Platform/AWS
pointers and preserve setting karpenterutil.ManagedByKarpenterLabel="true".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the analysis, but this is intentional. The human reviewer (@muraee) has explicitly and repeatedly clarified that Karpenter does not use NodePool spec fields — including nodePool.Spec.Platform.AWS.AMI. The Karpenter NodePool is a system-managed resource, so checking for a user-specified AMI override on it is unnecessary.

The current code correctly calls defaultNodePoolAMI() directly, as requested by the reviewer.


AI-assisted response via Claude Code

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skipped: comment is from another GitHub bot.

@bryan-cox

Copy link
Copy Markdown
Member

/verified by @jiezhao16

@openshift-ci-robot openshift-ci-robot added the verified Signifies that the PR passed pre-merge verification criteria label Feb 25, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@bryan-cox: This PR has been marked as verified by @jiezhao16.

Details

In response to this:

/verified by @jiezhao16

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@bryan-cox

Copy link
Copy Markdown
Member

/retest

@openshift-ci

openshift-ci Bot commented Feb 25, 2026

Copy link
Copy Markdown
Contributor

@hypershift-jira-solve-ci: all tests passed!

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@openshift-merge-bot
openshift-merge-bot Bot merged commit 0e711be into openshift:main Feb 25, 2026
22 checks passed
@openshift-ci-robot

Copy link
Copy Markdown

@hypershift-jira-solve-ci[bot]: Jira Issue Verification Checks: Jira Issue OCPBUGS-76350
✔️ This pull request was pre-merge verified.
✔️ All associated pull requests have merged.
✔️ All associated, merged pull requests were pre-merge verified.

Jira Issue OCPBUGS-76350 has been moved to the MODIFIED state and will move to the VERIFIED state when the change is available in an accepted nightly payload. 🕓

Details

In response to this:

What this PR does / why we need it:

Fixes a bug where the hypershift-operator ignores the AWS AMI override specified in nodePool.Spec.Platform.AWS.AMI when generating the token/user data in NewToken() (token.go). This causes NodePool deployments to fail in AWS ISO (classified) regions where:

  1. Release image metadata (StreamMetadata) may not contain region data for ISO regions
  2. Users must specify a custom AMI via nodePool.Spec.Platform.AWS.AMI to work in these regions

Changes:

  • token.go: Updated NewToken() to check nodePool.Spec.Platform.AWS.AMI before falling back to defaultNodePoolAMI(), aligning with the existing pattern in awsMachineTemplateSpec() (aws.go)
  • nodepool_controller.go: Added nil check for releaseImage.StreamMetadata in defaultNodePoolAMI() to prevent nil pointer dereference when stream metadata is unavailable (consistent with getWindowsAMI())
  • token_test.go: Added test cases for AMI override in token generation, including nil StreamMetadata scenario
  • nodepool_controller_test.go: Added test case for nil StreamMetadata in defaultNodePoolAMI()

Which issue(s) this PR fixes:

Fixes https://issues.redhat.com/browse/OCPBUGS-76350

Special notes for your reviewer:

The fix follows the same AMI resolution pattern already used in awsMachineTemplateSpec() (aws.go lines 41-58), ensuring consistency across both code paths. The bug was causing an inconsistency where the CAPI machine template path honored the AMI override but the token/user-data generation path did not.

Note: There is a pre-existing build failure in secret_janitor_test.go (references to undefined releaseinfo.NewMockProviderWithRegistryOverrides) that is unrelated to these changes.

Checklist:

  • Subject and description added to both, commit and PR.
  • Relevant issues have been referenced.
  • This change includes docs.
  • This change includes unit tests.

🤖 Generated with Claude Code via /jira-solve OCPBUGS-76350 origin

Always review AI generated responses prior to use.

Summary by CodeRabbit

  • Bug Fixes

  • Added validation to return an explicit error when a release image lacks stream metadata, preventing a nil dereference.

  • Tests

  • Added coverage for the release-image-without-stream-metadata scenario.

  • Refactor

  • AMI resolution moved to runtime reconciliation; AMI is no longer embedded in user-data and is set via labels during reconcile.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-merge-robot

Copy link
Copy Markdown
Contributor

Fix included in accepted release 4.22.0-0.nightly-2026-02-26-092444

@bryan-cox
bryan-cox deleted the fix-OCPBUGS-76350 branch April 6, 2026 17:09
@bryan-cox

Copy link
Copy Markdown
Member

/jira backport release-4.21,release-4.20

@openshift-ci-robot

Copy link
Copy Markdown

@bryan-cox: The following backport issues have been created:

Queuing cherrypicks to the requested branches to be created after this PR merges:
/cherrypick release-4.21
/cherrypick release-4.20

Details

In response to this:

/jira backport release-4.21,release-4.20

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-cherrypick-robot

Copy link
Copy Markdown

@openshift-ci-robot: #7675 failed to apply on top of branch "release-4.20":

Applying: fix(operator): honor AWS AMI override in NodePool token generation
Using index info to reconstruct a base tree...
M	hypershift-operator/controllers/nodepool/nodepool_controller.go
M	hypershift-operator/controllers/nodepool/token.go
Falling back to patching base and 3-way merge...
Auto-merging hypershift-operator/controllers/nodepool/token.go
CONFLICT (content): Merge conflict in hypershift-operator/controllers/nodepool/token.go
Auto-merging hypershift-operator/controllers/nodepool/nodepool_controller.go
error: Failed to merge in the changes.
hint: Use 'git am --show-current-patch=diff' to see the failed patch
hint: When you have resolved this problem, run "git am --continue".
hint: If you prefer to skip this patch, run "git am --skip" instead.
hint: To restore the original branch and stop patching, run "git am --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
Patch failed at 0001 fix(operator): honor AWS AMI override in NodePool token generation

Details

In response to this:

@bryan-cox: The following backport issues have been created:

Queuing cherrypicks to the requested branches to be created after this PR merges:
/cherrypick release-4.21
/cherrypick release-4.20

In response to this:

/jira backport release-4.21,release-4.20

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@openshift-cherrypick-robot

Copy link
Copy Markdown

@openshift-ci-robot: #7675 failed to apply on top of branch "release-4.21":

Applying: fix(operator): honor AWS AMI override in NodePool token generation
Using index info to reconstruct a base tree...
M	hypershift-operator/controllers/nodepool/nodepool_controller.go
M	hypershift-operator/controllers/nodepool/token.go
Falling back to patching base and 3-way merge...
Auto-merging hypershift-operator/controllers/nodepool/token.go
CONFLICT (content): Merge conflict in hypershift-operator/controllers/nodepool/token.go
Auto-merging hypershift-operator/controllers/nodepool/nodepool_controller.go
error: Failed to merge in the changes.
hint: Use 'git am --show-current-patch=diff' to see the failed patch
hint: When you have resolved this problem, run "git am --continue".
hint: If you prefer to skip this patch, run "git am --skip" instead.
hint: To restore the original branch and stop patching, run "git am --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
Patch failed at 0001 fix(operator): honor AWS AMI override in NodePool token generation

Details

In response to this:

@bryan-cox: The following backport issues have been created:

Queuing cherrypicks to the requested branches to be created after this PR merges:
/cherrypick release-4.21
/cherrypick release-4.20

In response to this:

/jira backport release-4.21,release-4.20

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

bryan-cox added a commit to bryan-cox/hypershift that referenced this pull request Apr 6, 2026
Backport of openshift#7675 to release-4.21.

The NewToken() function always called defaultNodePoolAMI() without
checking if a user-specified AMI override was set via
nodePool.Spec.Platform.AWS.AMI. This caused failures in AWS ISO
(classified) regions where release image metadata may not contain
region data, and users must specify a custom AMI.

This fix checks for the AMI override before falling back to
defaultNodePoolAMI(), and adds a nil check for StreamMetadata to
prevent nil pointer dereference when stream metadata is unavailable.
bryan-cox added a commit to bryan-cox/hypershift that referenced this pull request Apr 6, 2026
…n generation

Backport of openshift#7675 to release-4.21.

The NewToken() function always called defaultNodePoolAMI() without
checking if a user-specified AMI override was set via
nodePool.Spec.Platform.AWS.AMI. This caused failures in AWS ISO
(classified) regions where release image metadata may not contain
region data, and users must specify a custom AMI.

This fix checks for the AMI override before falling back to
defaultNodePoolAMI(), and adds a nil check for StreamMetadata to
prevent nil pointer dereference when stream metadata is unavailable.
@bryan-cox

Copy link
Copy Markdown
Member

The cherrypick to release-4.20 failed due to merge conflicts in token.go. A replacement PR with manual conflict resolution has been created: #8183

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. area/hypershift-operator Indicates the PR includes changes for the hypershift operator and API - outside an OCP release jira/severity-important Referenced Jira bug's severity is important for the branch this PR is targeting. jira/valid-bug Indicates that a referenced Jira bug is valid for the branch this PR is targeting. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. lgtm Indicates that a PR is ready to be merged. ok-to-test Indicates a non-member PR verified by an org member that is safe to test. verified Signifies that the PR passed pre-merge verification criteria

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants